#region (c) 2014 THN Solutions LLC. - All rights reserved
/*
THN DATA ABSTRACTION LAYER version 1

Copyright (c) 2014, THN Solutions LLC. ( www.ThnSolutions.com )
Author: Nguyễn, M. Hải                 ( www.Minh-Hai.com     )
All rights reserved.

This library is dual-licensed.
  + For commercial software, please obtain a commercial license from THN Solutions LLC.

  + For free software, this library is licensed under GPL version 3. A summary of GPLv3 is
    listed below. You should also find a copy of GPLv3 as file License_GPLv3.txt included
	with the source-code files.

    This program is free software: you can redistribute it and/or modify
    it under the terms of the GNU General Public License version 3 as published by
    the Free Software Foundation  of the License.

    This program is distributed in the hope that it will be useful,
    but WITHOUT ANY WARRANTY; without even the implied warranty of
    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
    GNU General Public License for more details.

    You should have received a copy of the GNU General Public License
    along with this program.  If not, see <http://www.gnu.org/licenses/>.
*/
#endregion

#region Using directives
using System;
using System.Collections;
using Thn.Data.Query;
using Thn.Data.Settings;
using Thn.Data.Mapping;
using Thn.Data.Exceptions;
using Thn.Application;
#endregion

namespace Thn.Data.Database.Memory
{
    /// <summary>
    /// A base database class that contains all data in memory
    /// </summary>
    public class InMemoryDatabase : AbstractDatabase
    {
        /// <summary>
        /// When true, exceptions will not be thrown regardless of DataTierSettings
        /// </summary>
        protected bool mSuppressException = false;

        #region I/O
        #region Initialize
        /// <summary>
        /// Prepare connection to database
        /// </summary>
        /// <param name="parameters">connection string</param>
        public override void Initialize(string parameters)
        {
        }
        #endregion

        #region Refresh
        /// <summary>
        /// Refresh/load data, if neccessary
        /// </summary>
        public override void Refresh()
        {
        }
        #endregion

        #region Flush
        /// <summary>
        /// Persist changes to database, if still buffered
        /// </summary>
        public override void Flush()
        {
        }
        #endregion

        #region Close
        /// <summary>
        /// Close connection to the underlying database, if neccessary
        /// </summary>
        public override void Close()
        {
        }
        #endregion
        #endregion

        #region Transaction
        #region Begin Transaction
        /// <summary>
        /// Start a new transaction.
        /// </summary>
        /// <returns>The transaction context specific to database</returns>
        public override object BeginTransaction()
        {
            return null;
        }
        #endregion

        #region Commit Transaction
        /// <summary>
        /// Execute task(s) contained in this transaction and persist changes to database
        /// </summary>
        /// <param name="transactionContext">The transaction context specific to database</param>
        public override void CommitTransaction(object transactionContext)
        {
            
        }
        #endregion

        #region Cancel Transaction
        /// <summary>
        /// Abandon changes made within this transaction
        /// </summary>
        /// <param name="transactionContext">The transaction context specific to database</param>
        public override void CancelTransaction(object transactionContext)
        {
        }
        #endregion
        #endregion

        #region Structuring
        #region Create structure for 1 object type
        /// <summary>
        /// Generate database structure specific to a single object type
        /// </summary>
        /// <param name="objectType">type of data object to generate</param>
        /// <param name="keepOldData">try to retain the existing data</param>
        public override void CreateStructure(Type objectType, bool keepOldData)
        {
        }
        #endregion

        #region Create all structures
        /// <summary>
        /// Generate database structures (table, stored procedure, etc.) of all registered object types
        /// </summary>
        /// <param name="keepOldData">try to retain the existing data</param>
        public override void CreateStructure(bool keepOldData)
        {
            foreach (Type t in RegisteredTypes)
            {
                CreateStructure(t, keepOldData);
            }
        }
        #endregion
        #endregion

        #region Execute query command
        #region Execute base command
        /// <summary>
        /// Proccess a query command
        /// </summary>
        /// <param name="command">The command to process</param>
        public override ExecuteResult Execute(QueryBase command)
        {
            ExecuteResult result = null;

            if (command is InsertQuery) Execute((InsertQuery)command);
            else if (command is UpdateQuery) Execute((UpdateQuery)command);
            else if (command is DeleteQuery) Execute((DeleteQuery)command);
            else if (command is SelectQuery)
            {
                result = Execute((SelectQuery)command);
            }
            else if (command is TransactionQuery)
            {
                result = Execute((TransactionQuery)command);
            }

            return result;
        }
        #endregion

        #region Execute Insert query
        /// <summary>
        /// Process an insert query command
        /// </summary>
        /// <param name="command">The command to process</param>
        protected virtual void Execute(InsertQuery command)
        {
            //pre-condition
            object obj = command.InsertClause;
            if (command.InsertClause == null)
            {
                if (!mSuppressException)
                {
                    NoValueInQueryException.Publish();
                }
            }
            else InsertObjects((IList)obj);
        }
        #endregion

        #region Execute Update query
        /// <summary>
        /// Process an update query command
        /// </summary>
        /// <param name="command">The command to process</param>
        protected virtual void Execute(UpdateQuery command)
        {
            //pre-condition
            if (command.UpdateClause == null)
            {
                if (!mSuppressException)
                {
                    NoValueInQueryException.Publish();
                }
            }

            IList values = (IList)command.UpdateClause;

            UpdateObjects(values);
        }
        #endregion

        #region Execute Delete query
        /// <summary>
        /// Process a delete query command
        /// </summary>
        /// <param name="command">The command to process</param>
        protected virtual void Execute(DeleteQuery command)
        {
            //pre-condition            
            if ((command.DeleteClause == null) && (command.FromClause == null))
            {
                if (!mSuppressException)
                {
                    NoValueInQueryException.Publish();
                }
            }

            //If objects to be delete has been specified, delete them by identifiers
            if (command.DeleteClause != null)
            {
                //objects to delete has been specified
                IList values = (IList)command.DeleteClause;

                DeleteObjects(values);
            }
            else
            {//Objeccts to be delete has not been specified, delete them by condition
                IList data = GetObjects((Type)command.FromClause);
                Condition con = command.WhereClause;
                if (con == null) data.Clear();
                else
                {
                    IList matched = new ArrayList();
                    foreach (object tmp in data)
                    {
                        matched.Add(tmp);
                    }

                    if (matched.Count > 0) DeleteObjects(matched);
                }
            }
        }
        #endregion

        #region Execute Select query
        #region Select Single Entity
        /// <summary>
        /// Process select query command for one single entity
        /// </summary>
        /// <param name="entity">Object mapping</param>
        /// <param name="command">query command</param>
        /// <returns>List of result. If no records returned, list is empty</returns>
        protected virtual ExecuteResult SelectSingleEntity(ObjectMap entity, SelectQuery command)
        {
            IList result = null;
            Condition con = command.WhereClause;

            Type t = entity.ObjectEntity;
            result = SelectObjects(t, con);

            return new ExecuteResult(result);
        }
        #endregion

        #region Select Multi-entity
        /// <summary>
        /// Procecss select query for multi-entity query
        /// </summary>
        /// <param name="entities">entities to be queried</param>
        /// <param name="command">query command</param>
        /// <returns>TBD</returns>
        protected virtual ExecuteResult SelectMultiEntity(ObjectMap[] entities, SelectQuery command)
        {
            throw new NotSupportedException("Sorry multi-table selection is not yet supported");
        }
        #endregion

        #region Execute Select
        /// <summary>
        /// Process a select query command
        /// </summary>
        /// <param name="command">The command to process</param>
        protected virtual ExecuteResult Execute(SelectQuery command)
        {
            ExecuteResult result = null;

            //pre-condition
            if (command.FromClause == null)
            {
                if (!mSuppressException)
                {
                    QuerySyntaxException.Publish(DataExceptionConstants.EntityNotSpecified);
                }
            }

            //get entities
            ObjectMap[] entities = new ObjectMap[command.FromClause.Count];
            for (int i = 0; i < command.FromClause.Count; i++)
            {
                entities[i] = mMapper.GetEntity((Type)command.FromClause[i]);
            }

            //process query
            if (entities.Length == 1) result = SelectSingleEntity(entities[0], command);
            else result = SelectMultiEntity(entities, command);

            return result;
        }
        #endregion
        #endregion

        #region Execute Transaction query
        /// <summary>
        /// Process a transaction query command
        /// </summary>
        /// <param name="command">The command to process</param>
        protected virtual ExecuteResult Execute(TransactionQuery command)
        {
            ExecuteResult result = null;

            object scope = BeginTransaction();

            QueryCollection queries = command.Transaction.Commands;

            //make sure that error is always thrown so that I can handle transaction consistently
            bool silent = DataTierSettings.SilentError;
            DataTierSettings.SilentError = false;

            try
            {
                for (int i = 0; i < queries.Count; i++)
                {
                    ExecuteResult newResult = Execute(queries[i]);
                    result = ProcessResult(result, newResult);
                }

                //If I can go this far without exceptions, I can commit now
                CommitTransaction(scope);

                //reverse the state of silent settings
                if (silent) DataTierSettings.SilentError = silent;
            }
            catch (Exception error)
            {
                //uh oh, exception occured, I'll have to rollback the transaction
                CancelTransaction(scope);

                //reverse the state of silent settings
                if (silent) DataTierSettings.SilentError = silent;

                //process error
                if (!mSuppressException)
                {
                    if (DataTierSettings.AutoPublishError) Log.Error(error);
                    if (!silent) throw error;
                }
            }

            return result;
        }
        #endregion
        #endregion

        #region Insert
        #region Insert object(s)
        /// <summary>
        /// Add new object(s) into database
        /// </summary>
        public override void InsertObjects(params object[] values)
        {
            foreach (object obj in values)
            {
                OnInsert(obj);
            }
        }

        #endregion

        #region Insert collection
        /// <summary>
        /// Add a collection of objects to database
        /// </summary>
        public override void InsertObjects(IList values)
        {
            foreach (object obj in values)
            {
                OnInsert(obj);
            }
        }
        #endregion

        #region On Insert
        /// <summary>
        /// Insert an object to database
        /// </summary>
        /// <param name="value">the data object to insert</param>
        protected virtual void OnInsert(object value)
        {
            if (value != null)
            {
                Type t = value.GetType();
                IList master = null;

                //find the master list for the type
                if (!mObjectByTypes.Contains(t))
                {
                    master = new ArrayList();
                    mObjectByTypes.Add(t, master);
                }
                else master = (IList)mObjectByTypes[t];

                //insert object to master
                master.Add(value);

                //register type
                if (!IsRegistered(t) && AutoRegister) Register(t);
            }
        }
        #endregion
        #endregion

        #region Update
        #region Update object(s)
        /// <summary>
        /// Update object(s) into database
        /// </summary>
        public override void UpdateObjects(params object[] values)
        {
            foreach (object tmp in values)
            {
                OnUpdate(tmp);
            }
        }
        #endregion

        #region Update collection
        /// <summary>
        /// Update a collection of objects to database
        /// </summary>
        public override void UpdateObjects(IList values)
        {
            foreach (object tmp in values)
            {
                OnUpdate(tmp);
            }
        }
        #endregion

        #region On Update
        /// <summary>
        /// Modify changes to an object
        /// </summary>
        protected virtual void OnUpdate(object value)
        {
        }
        #endregion
        #endregion

        #region Delete
        /// <summary>
        /// Delete object(s) from database using their identifiers
        /// </summary>
        /// <param name="values">objects to delete</param>
        public override void DeleteObjects(params object[] values)
        {
            foreach (object tmp in values)
            {
                OnDelete(tmp);
            }
        }

        /// <summary>
        /// Delete object(s) from database using their identifiers
        /// </summary>
        /// <param name="values">objects to delete</param>
        public override void DeleteObjects(IList values)
        {
            foreach (object tmp in values)
            {
                OnDelete(tmp);
            }
        }

        #region On Delete
        /// <summary>
        /// Remove an object from database
        /// </summary>
        protected virtual void OnDelete(object value)
        {
            if (value != null)
            {
                Type t = value.GetType();
                IList master = GetObjects(t);

                //remove object from master
                master.Remove(value);
            }
        }
        #endregion
        #endregion

        #region Select
        /// <summary>
        /// Get object(s) matching the condition
        /// </summary>
        /// <param name="objectType">Type of object to query against</param>
        /// <param name="condition">Query criteria</param>
        /// <returns>A collection is always returned. If there are no object matches the condition, the collection is empty</returns>
        public override IList SelectObjects(Type objectType, Condition condition)
        {
            IList result = new ArrayList();
            IList data = GetObjects(objectType);

            //linear search for objects matching condition
            if (condition == null) result = data;
            else
            {
                foreach (object obj in data)
                {
                    if (Condition.Match(obj, condition)) result.Add(obj);
                }
            }

            return result;
        }
        #endregion

        #region Process result
        /// <summary>
        /// Combine results of different query.
        /// At this time, this function is not complete, it will always return the new result (if not null)
        /// </summary>
        /// <param name="oldResult">current result set</param>
        /// <param name="newResult">new result set</param>
        /// <returns></returns>
        protected virtual ExecuteResult ProcessResult(ExecuteResult oldResult, ExecuteResult newResult)
        {
            ExecuteResult result = oldResult;

            if (newResult != null) result = newResult;

            return result;
        }
        #endregion

        #region Object Caching
        /// <summary>
        /// A cache of objects by types
        /// <para>key   : Type</para>
        /// <para>value : ArrayList of objects</para>
        /// </summary>
        Hashtable mObjectByTypes = new Hashtable();

        #region Get Objects
        /// <summary>
        /// Get all objects of a specific type
        /// </summary>
        /// <param name="objectType">type of data object</param>
        protected IList GetObjects(Type objectType)
        {
            IList result = null;

            if (mObjectByTypes.Contains(objectType)) result = (ArrayList)mObjectByTypes[objectType];
            else
            { //not cached yet, ask my descendant to load all objects of this type for me
                result = OnGetObjects(objectType);
                if (result != null) mObjectByTypes.Add(objectType, result);
            }

            if (result == null) result = new ArrayList();

            return result;
        }
        #endregion

        #region Load Objects
        /// <summary>
        /// Get all objects of a specific type
        /// </summary>
        /// <param name="objectType">type of data object</param>
        protected virtual IList OnGetObjects(Type objectType)
        {
            return null;
        }
        #endregion

        #endregion

        #region Constructors
        /// <summary>
        /// Default constructor
        /// </summary>
        public InMemoryDatabase()
        {
        }
        #endregion
    }
}
